LBB

构建优秀的 NPM 包

# 构建优秀的 NPM 包 ```txt // https://twitter.com/acemarke/status/1652021889307496448 Things I have to keep in mind when publishing a library in 2023: - Build artifact formats (ESM, CJS, UMD) - Matrixed with: dev/prod/NODE_ENV builds - Bundled or individual .js per source - `exports` setup - Webpack 4 limits - TS `moduleResolution` options - User environments ``` ## 一个优秀的 npm 包应该具有哪些特性 - 同时提供 CommonJS、ESM 支持 - 提供 Typescript 类型支持 - 精简的体积 - Tree Shaking - 压缩 - 发布仅包含关键文件 - 明确依赖和使用环境 - 安全 - 支持 esm.sh、unpkg - 令人舒服的 LICENSE ## 拆解 ### 模块导出及类型 由于历史因素的存在,Node.js 最早支持的模块类型为 CommonJS,随着 ES6 的发布带来全新的 ESM 模块类型,整个 JS 生态也在逐渐向 ESM 靠拢。比如 Vite 已经表示从 v6 版本开始,不再提供 CJS 导出的 Vite 包(<https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated)。推荐将新的模块都按照> ESM 模块编写,并在 package.json 中添加 `"type": "module"`,加速整个 ESM 模块生态的发展。 ESM 中有一些特性在 CommonJS 中是不支持的,因此无法在 CommonJS 项目中引入 ESM 模块,但 ESM 模块可以通过一些手段引用 CommonJS。 但事实上由于 npm 生态及公司业务中仍然有大量存量的 CommonJS 模块和项目,为了兼容性考虑,也可以不添加 "type": "model",通过 package.json 中的 `module`、`exports` 等字段提供 ESM 的兼容。 #### 主流的导出优先级为:[^5] ##### Node.js export > main ##### 打包工具 browser > export > module > main #### ESM only ```json { "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts" "export": "dist/index.js", } ``` #### CJS 兼容 ESM - mian 导出 UMD,同时支持 CJS 和 AMD,并且会导出全局变量方便在浏览器中直接使用。 - module 字段并没有被 node.js 官方纳入标准,但类似 Rollup、Webpack 等打包工具会判断这个字段。<https://stackoverflow.com/questions/42708484/what-is-the-module-package-json-field-for> - export 字段非常强大。<https://nodejs.org/api/packages.html#conditional-exports> - 要为 .mjs 单独提供一份 .mts 文件作为类型。<https://publint.dev/rules#export_types_invalid_format> ```json { "main": "dist/index.umd.min.js", "types": "dist/index.d.ts", "module": "dist/index.mjs", "export": { ".": { "require": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" } } } } ``` ### 精简体积 #### Three shaking 主流的打包工具都支持 three shaking,前提是使用 `import` 和 `export`,依赖必须兼容 ESM 的导出。由于 JavaScript 过于灵活,所以它有两个概念 usedExports 和 sideEffects。 - 模块要提供 ESM 的导出 - 无副作用 - package.json 中 "sideEffects": false or "sideEffects": ["src/xx.js"],代表的是模块级别的无副作用,即整个项目或某些文件中的所有代码都无副作用。 - terser 中可以使用 `/*#**__PURE*__/` 的行内注解表示具体那些代码是无副作用的 更详细的内容: <https://webpack.js.org/guides/tree-shaking> #### 压缩 基于 terser 和打包工具的普遍性,也为了方便对程序进行调试,大部分产物都不需要进行压缩。 umd 提供在浏览器中直接使用的可能性,尽可能提供 minify 后的版本。 #### 仅发布有用的文件 通过配置 package.json 中的 files 字段,来指定发布包含哪些文件,减少使用包的人的下载时间和磁盘占用。 ```json { "files": ["dist"] } ``` ### 明确依赖和使用环境 TODO ### 安全 一个库中很可能引用一些其他的库,为了确保不会存在明显的安全问题。需要一些手段检测代码本身或依赖是否存在漏洞。 - <https://docs.npmjs.com/cli/v10/commands/npm-audit> - <https://snyk.io> ### CDN 分发的特有配置 TODO ### 选择 LICENSE 可参考阮一峰的 <https://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html> 推荐一种比 MIT 限制更宽松的 LICENSE: Unlicense <https://unlicense.org/> ### 检查 package.json 的工具 检查包对各种打包工具的兼容性及配置路径是否正确 - [publint](https://www.npmjs.com/package/publint) - [arethetypeswrong/cli](@arethetypeswrong/cli) ## Tools <https://npmgraph.js.org> pcakge.json 依赖可视化 <https://pkg-size.dev> 快速查看包体积 <https://bundlephobia.com> 包体积,带有 badge ### 参考 [1] <https://github.com/frehner/modern-guide-to-packaging-js-library> [2] <https://blog.isquaredsoftware.com/2023/08/esm-modernization-lessons/> [3] <https://github.com/stereobooster/package.json> [4] <https://esbuild.github.io/api/#main-fields> [5] <https://juejin.cn/post/7225072417532739644> [6] <https://www.totaltypescript.com/how-to-create-an-npm-package>
构建优秀的 NPM 包 | LBB